// Sample solution for Practice Program 3: Throwing Rocks
// $Id$

#include <iostream>			// for cin, cout
#include <windows.h>		// for Sleep()
#include <conio.h>			// for _kbhit()
#define _USE_MATH_DEFINES
#include <math.h>			// for M_PI


using namespace std;

// use 2 dimensional vectors.
struct Vector2D
{
	float x, y;

	// default constructor
	Vector2D()
	{
		x = y = 0.0f;
	}
	// initializing constructor
	Vector2D(float x, float y)
		:x(x), y(y)
	{};
};


const float DEGREES2RADIANS = (float)M_PI / 180.0f;
const float MPH2FPS = 5280.0f / 3600.0f;
const float GRAVITY = -32.0f;


void OneDTimeStep(float fDeltaTime, float* pfDistance, float* pfSpeed, float fAcceleration)
{
	// newSpeed is oldSpeed + (acceleration * time)
	*pfSpeed += fAcceleration * fDeltaTime;
	// newDistance is oldDistance + (newSpeed * time)
	*pfDistance += *pfSpeed * fDeltaTime;
}


void TwoDTimeStep(float fDeltaTime, Vector2D* pvDistance, Vector2D* pvSpeed, Vector2D& vAcceleration)
{
	// simulate vertical and horizontal components separately
	OneDTimeStep(fDeltaTime, &(pvDistance->x), &(pvSpeed->x), vAcceleration.x);
	OneDTimeStep(fDeltaTime, &(pvDistance->y), &(pvSpeed->y), vAcceleration.y);
}


float RunSimulation(Vector2D* pvDistance, Vector2D* pvSpeed, Vector2D vAcceleration, float fTimeIncrement, float* pfMaxHeight, float* pfMaxHeightTime)
{
	// Saves the computed values back in vDistance and vSpeed

	// Keep track of total simulated time
	float fTotalTime = 0.0f;

	// Keep track of highest point
	*pfMaxHeight = 0.0f;
	*pfMaxHeightTime = 0.0f;

#ifdef _DEBUG
cout << "[at time " << fTotalTime << ", distance is " << pvDistance->x << " ft, height is " << pvDistance->y << " ft]" << endl;
#endif

	// simulate until the rock hits the ground
	while (pvDistance->y > 0)
	{
		fTotalTime += fTimeIncrement;

		// step the simulation by one time step
		TwoDTimeStep(fTimeIncrement, pvDistance, pvSpeed, vAcceleration);

#ifdef _DEBUG
		cout << "[at time " << fTotalTime << ", distance is " << pvDistance->x << " ft, height is " << pvDistance->y << " ft]" << endl;
#endif

		// check for new maximum height
		if (pvDistance->y > *pfMaxHeight)
		{
			*pfMaxHeight = pvDistance->y;
			*pfMaxHeightTime = fTotalTime;
		}
	}
#ifdef _DEBUG
	cout << "[maximum height was " << *pfMaxHeight << " feet at " << *pfMaxHeightTime << " seconds]" << endl;
#endif

	// simulation is done
	return fTotalTime;
}

float HorizontalDistance(float fHorizontalSpeed, float fDeltaTime)
{
	// Distance(t) = InitialDistance + InitialHorizontalSpeed x t + 1/2 x HorizontalAcceleration x squared(time)
	// for the boss throwing rocks, InitialDistance is 0 (it is in his hand to start),
	// and HorizontalAcceleration is 0
	// so Distance(t) = 0 + InitialHorizontalSpeed x t + 0 = InitialHorizontalSpeed x t
	return fHorizontalSpeed * fDeltaTime;
}

float TimetoGround(Vector2D& vInitialSpeed, float fInitialHeight)
{
	// compute theoretical values for comparison

	// Height(t) = InitialHeight + InitialVerticalSpeed x t + 1/2 x VerticalAcceleration x squared(time)
	// for the boss throwing rocks, VerticalAcceleration is Gravity
	// the rock hits the ground when Height(t) is zero
	// use the quadratic formula to solve for t when H(t) = 0
	float A = 0.5f * GRAVITY;
	float B = vInitialSpeed.y;
	float C = fInitialHeight;

	float fDescriminator = (float)sqrt((B * B) - (4.0f * A * C));
	float fPlusRoot = (-B + fDescriminator) / (2.0f * A);
	float fMinusRoot = (-B - fDescriminator) / (2.0f * A);
	return (fPlusRoot >= 0.0f) ? fPlusRoot : fMinusRoot;
}

float TimeToHighest(float fInitialVerticalSpeed, float fVerticalAcceleration)
{
	// At the highest point, vertical speed is 0
	// verticalSpeed = deriv(Height)
	// verticalSpeed = InitialVerticalSpeed + VerticalAcceleration x time
	// at the highest point, 0 = InitialVerticalSpeed + VerticalAcceleration x time
	// VerticalAcceleration x time = -InitialVerticalSpeed
	// time = InitialVerticalSpeed / -VerticalAcceleration
	return fInitialVerticalSpeed / (-1 * fVerticalAcceleration);
}

void main()
{
	// get initial height input
	float fInitialHeight;
	cout << "Rock starting height (in feet): ";
	cin >> fInitialHeight;

	// rock starts in boss' hand
	//	(horizontal distance = 0, vertical distance = boss' height)
	Vector2D vInitialDistance(0.0f, fInitialHeight);

	// get initial speed in the direction thrown
	float fMPH;
	cout << "Throwing speed (in mph): ";
	cin >> fMPH;
	float fSpeed = fMPH * MPH2FPS;
#ifdef _DEBUG
	cout << "(" << fSpeed << " feet per second)" << endl;
#endif

	// get throw angle
	float fInitialDegrees;
	cout << "Throwing angle (in degrees): ";
	cin >> fInitialDegrees;
	float fTheta = fInitialDegrees * DEGREES2RADIANS;

	// initial speed is in the direction of the throw
	// get the horizontal and vertical components
	Vector2D vInitialSpeed(fSpeed * cos(fTheta), fSpeed * sin(fTheta));
#ifdef _DEBUG
	cout << "(vertical component is " << vInitialSpeed.y << " feet per second)" << endl;
	cout << "(horizontal component is " << vInitialSpeed.x << " feet per second)" << endl;
#endif

	// acceleration is constant: nothing horizontal and gravity vertical
	Vector2D vAcceleration(0.0f, GRAVITY);

	// get simulation time step size
	float fDeltaTime;
	cout << "Simulation time step (in seconds): ";
	cin >> fDeltaTime;

	// run the simulation!
	float fHighest;
	float fHighestTime;
	// (these are changed by the simulation)
	Vector2D vDistance = vInitialDistance;
	Vector2D vSpeed = vInitialSpeed;
	float fSimulatedTime = RunSimulation(&vDistance, &vSpeed, vAcceleration, fDeltaTime, &fHighest, &fHighestTime);
	cout << "In simulation, the rock travels " << vDistance.x << " feet in " << fSimulatedTime << " seconds." << endl;
	cout << "It reaches a peak of " << fHighest << " feet in " << fHighestTime << " seconds." << endl;

	// the first optional part
	float fTheoreticalTime = TimetoGround(vInitialSpeed, fInitialHeight);
	float fPercentError = 100.0f * fabs(fTheoreticalTime - fSimulatedTime) / fTheoreticalTime;
	cout << endl << "Theoretical time for rock to hit the ground is " << fTheoreticalTime << " seconds." << endl;
	cout << "Simulated time for rock to hit the ground is " << fSimulatedTime << " seconds." << endl;
	cout << "Simulation error: " << fPercentError << "%" << endl << endl;

	float fTheoreticalDistance = HorizontalDistance(vSpeed.x, fTheoreticalTime);
	float fSimulatedDistance = vDistance.x;
	fPercentError = 100.0f * fabs(fTheoreticalDistance - fSimulatedDistance) / fTheoreticalDistance;
	cout << "Theoretical distance for rock to travel is " << fTheoreticalDistance << " feet." << endl;
	cout << "Simulated distance for rock to travel is " << fSimulatedDistance << " feet." << endl;
	cout << "Simulation error: " << fPercentError << "%" << endl << endl;

	fTheoreticalTime = TimeToHighest(vInitialSpeed.y, vAcceleration.y);
	fPercentError = 100.0f * fabs(fTheoreticalTime - fHighestTime) / fTheoreticalTime;
	cout << "Theoretical time to the highest point is " << fTheoreticalTime << " seconds." << endl;
	cout << "Simulated time to the highest point is " << fHighestTime << " seconds." << endl;
	cout << "Simulation error: " << fPercentError << "%" << endl << endl;

	// keep the console window from closing too soon
	while (! _kbhit())
	{
		Sleep(1000);
	}
}